07 - 映射TMap
本文主要说明了UE5中有关容器类映射TMap
的相关概念和常见操作。
TMap
TMap
主要由两个类型定义(一个键类型和一个值类型),以关联对(TPair<KeyType, ValueType>
)的形式存储在映射中。TMap
是一种哈希容器,也就是说键类型必须支持GetTypeHash()
函数,并提供operator==
来比较每个键是否等值。
此外,内存中TMap
元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同,这些元素也不太可能在内存中连续排列。
TMultiMap
TMap
只支持存储一个相同的键,重复存储将导致值的替换。而TMultiMap
支持存储多个相同的键。
声明
声明一个TMap
的方法如下:
TMap<int32, FString> FruitMap;
添加元素
和TArray
类似,可以用Add(K, V)
,Emplace(K, V)
往TMap
中添加元素;可用Append(Map2)
将映射Map2
中的所有元素移动到调用者中。
要想获取值,可通过operator[]
以键为索引查找对应的值。
遍历元素
TMap
支持基于范围和基于迭代器的遍历:
// 基于范围的遍历:
for (auto& Elem : FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
// 基于迭代器的遍历:
// CreateIterator(): 读写访问
// CreateConstIterator(): 只读访问
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
It.Key(), // same as It->Key
*It.Value() // same as *It->Value
)
);
}
排序
如果之后不再对映射进行修改,可通过KeySort()
和ValueSort()
函数对映射分别按键和值排序:
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});
FruitMap.ValueSort([](const FString& A, const FString& B) {
return A.Len() < B.Len(); // sort strings by length
});
查询
TMap
还提供了一系列查询函数,方便用户进行相关操作:
- 询问映射属性:
函数 | 用途 |
---|---|
Num() | 确认映射中保存的元素数量 |
Contains() | 确认映射中是否包含特定键 |
GenerateKeyArray() | 获取映射键的数组 |
GenerateValueArray() | 获取映射值的数组 |
CountBytes() 和GetAllocatedSize() | 用于估计内部数组的当前内存使用情况 |
Dump() | 输出关于映射内容的实现信息,用于调试 |
- 键值查询类:
函数 | 用途 |
---|---|
Find() | 如果映射包含该键,返回指向元素值的指针 |
FindOrAdd() | 找不到就用该键和默认值创建新元素,并且返回值的引用 |
FindRef() | 找不到不会创建新元素,而是返回临时构建的默认值;找到则返回对应值的副本 |
FindKey() | 寻找并返回首个拥有该值的键的指针,找不到返回空指针 |
移除
TMap
提供了一系列移除函数:
函数 | 用途 |
---|---|
Remove() | 根据键移除指定元素,返回移除元素的数量 |
FindAndRemoveChecked() | 根据键移除指定元素,返回移除元素值;找不到会触发Assert() |
RemoveAndCopyValue() | 根据键移除指定元素,将值复制到引用参数中,最后以bool 返回是否成功 |
Empty() 和Reset() | 同TArray 中的相关函数 |
需要注意的是,移除一个元素后,TMap
仍会保留该元素之前所在的内存空间。
运算符
TMap
提供如下运算符:
operator=
:拷贝赋值;MoveTemp()
:移动赋值;
内存管理
Slack
和TArray
类似,TMap
也有Slack
这个概念。Slack
优化了将新元素添加到映射的过程,因为可以使用它预分配的内存,而不是分配新内存;在移除元素时也十分实用,因为系统不需要真正取消被移除元素的内存分配。
Reserve()
,Reset()
和Empty()
函数的用途和TArray
类似。
需要注意的是Shrink()
函数的使用方法:先Compact()
,后Shrink()
。因为Shrink()
只会移除容器末端的Slack
,容器元素间的Slack
它无法移除,而Compact()
则会整理这些空白空间至容器末尾。
编写KeyFuncs
KeyFuncs
是最后一个TMap
模板参数,该参数告知映射如何从元素类型获取键,如何比较两个键是否相等,以及如何对键进行哈希计算。如果键类型是自定义的类或结构体,就需要为它自行编写KeyFuncs
。
要为键编写KeyFuncs
,需要定义两个类型别名和三个静态函数:
类型别名 | 说明 |
---|---|
KeyInitType | 传递键的类型 |
ElementInitType | 传递元素的类型 |
静态函数 | 说明 |
---|---|
KeyInitType GetSetKey(ElementInitType Element) | 返回元素的键 |
bool Matches(KeyInitType A, KeyInitType B) | 返回A == B 的结果 |
uint32 GetKeyHash(KeyInitType Key) | 返回Key 的哈希值 |
这里以自定义结构体类MyStruct
为例:
struct FMyStruct
{
// String which identifies our key
FString UniqueID;
// Some state which doesn't affect struct identity
float SomeFloat;
explicit FMyStruct(float InFloat)
: UniqueID (FGuid::NewGuid().ToString())
, SomeFloat(InFloat)
{
}
};
它的KeyFuncs
如下:
template <typename ValueType>
struct TMyStructMapKeyFuncs :
BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
>
{
private:
typedef BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
> Super;
public:
typedef typename Super::ElementInitType ElementInitType;
typedef typename Super::KeyInitType KeyInitType;
static KeyInitType GetSetKey(ElementInitType Element)
{
return Element.Key.UniqueID;
}
static bool Matches(KeyInitType A, KeyInitType B)
{
return A.Compare(B, ESearchCase::CaseSensitive) == 0;
}
static uint32 GetKeyHash(KeyInitType Key)
{
return FCrc::StrCrc32(*Key);
}
};
可以看到,创建自定义KeyFuncs
的一般步骤如下:
- 继承
BaseKeyFuncs
(因为它定义了KeyInitType
,ElementInitType
等类型),它使用两个模板参数:- 映射的元素类型:即
TPair<KeyType, ValueType>
,这里使用FMyStruct
带入参数KeyType
,使用KeyFuncs
的模板参数ValueType
带入ValueType
以保证泛用性。 KeyFuncs
专用的键类型:这里使用FMyStruct::UniqueID
作为判等的依据,因此是FString
。
- 映射的元素类型:即
- 定义两个类型别名:上述代码中
Super
相关操作。 - 实现三个静态函数:
GetSetKey()
:返回专用的键类型,这里是FMyStruct::UniqueID
;Matches()
:判等专用的键类型,这里是比较两个FString
类型;GetKeyHash()
:计算专用键的哈希值,这里是FString
的哈希值;
最后就能创建KeyType
为FMyStruct
类型,ValueType
为任意类型的TMap
了:
TMap<
FMyStruct,
int32,
FDefaultSetAllocator,
TMyStructMapKeyFuncs<int32>
> MyMapToInt32;
// Add some elements
MyMapToInt32.Add(FMyStruct(3.14f), 5);
MyMapToInt32.Add(FMyStruct(1.23f), 2);
// MyMapToInt32 == [
// {
// Key: {
// UniqueID: "D06AABBA466CAA4EB62D2F97936274E4",
// SomeFloat: 3.14f
// },
// Value: 5
// },
// {
// Key: {
// UniqueID: "0661218447650259FD4E33AD6C9C5DCB",
// SomeFloat: 1.23f
// },
// Value: 5
// }
// ]